/*
 * Copyright 2009 Brook Smith brook.smith@paradise.net.nz.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.javagenerate.generator.script;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.javagenerate.generator.MethodDefinition;
import org.javagenerate.generator.script.gen.NonBeanClass;

import de.hunsicker.jalopy.Jalopy;

public abstract class BasePatternGenerator implements PatternGenerator {
	private List<GenBeanDefinition> properties = new ArrayList<GenBeanDefinition>();
	private List<String> imports = new ArrayList<String>();
	private ClassDef currentClass = null;
	private Map<String, ClassDef> classDefs = new HashMap<String, ClassDef>();
	private Map<String, String> templateDefs = new HashMap<String, String>();
	private MethodDefinition methodDefinition;
	private Properties templateProperties = null;
	private String patternName;

	public BasePatternGenerator(Properties templateProperties, String patternName) {
		super();
		this.templateProperties = templateProperties;
		this.patternName = patternName;
	}

	public Properties getTemplateProperties() {
		return templateProperties;
	}

	public ClassDef getClass(String type) {
		return classDefs.get(type);
	}

	public ClassDef addClass(String type, ClassDef classDef) {
		return classDefs.put(type, classDef);
	}

	public List<GenBeanDefinition> getProperties() {
		return properties;
	}

	public List<String> getImports() {
		return imports;
	}

	public ClassDef getCurrentClass() {
		return currentClass;
	}

	public Map<String, ClassDef> getClassDefs() {
		return classDefs;
	}

	public MethodDefinition getMethodDefinition() {
		return methodDefinition;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.wgs.generator.script.gen.PatternGenerator#addBeanDefinition(java.
	 * util.List)
	 */
	public void addBeanDefinition(List<String> props) {
		properties.add(getBeanDefinition(props));
	}

	/**
	 * Return an instance of the class for the type
	 * 
	 * @param type
	 * @return
	 */
	public abstract ClassDef getClassdef(String type, String classname);

	/**
	 * returns an instance of the default definitition for the class requested
	 * 
	 * @param type
	 * @return
	 */
	public ClassDef getDefaultClassdef(String type, String classname) {
		ClassDef classDef = null;
		if (type.equals("dto") || type.equals("bean")) {
			return new ClassDef(classname, patternName);
		} else if (type.equals("nonbean")) {
			return new NonBeanClass(classname, patternName);
		}
		return null;
	}

	public String getPatternName() {
		return patternName;
	}

	public void addTemplateDefinition(List<String> props) {
		String resourceName = "";
		String packageName = "";
		String simpleName = "";
		String last = "";
		int pos = 0;
		for (String string : props) {
			string = string.trim();
			if (string.length() < 1) {
				continue;
			}
			if (last.equals("/") || string.equals("/") || last.equals("\\") || string.equals("\\")) {
				resourceName += string;
			} else if (packageName.length() > 0) {
				simpleName = string;
			} else if (resourceName.length() > 0) {
				packageName = string.replace('.', '/');
			} else {
				resourceName += string;
			}
			last = string;
		}
//		System.out.println(resourceName + ", " + packageName + ", " + simpleName);
		templateDefs.put(packageName + "/" + simpleName, resourceName);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.wgs.generator.script.gen.PatternGenerator#addClassDefinition(java
	 * .util.List)
	 */
	public void addClassDefinition(List<String> props) {
		// class Factory
		String type = null;
		String name = null;
		List<String> options = new ArrayList<String>();

		for (String string : props) {
			if (string.equals(" ")) {
				// just ignore
			} else if (type == null) {
				type = string;
			} else if (name == null) {
				name = string;
			} else {
				options.add(string);
			}
		}

		ClassDef classDef = getClassdef(type, name);

		if (classDef != null) {
			classDefs.put(type, classDef);
			currentClass = classDef;
			classDef.getOptions().addAll(options);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.wgs.generator.script.gen.PatternGenerator#addClassImport(java.util
	 * .List)
	 */
	public void addClassImport(List<String> unresolvedTokens) {
		currentClass.getImports().add(unresolvedTokens.get(0));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.wgs.generator.script.gen.PatternGenerator#addClassMethod(java.util
	 * .List)
	 */
	public void addClassMethod(List<String> content) {
//		System.out.println("{methodDefinition:" + content + "}");
		methodDefinition = MethodUtils.parseMethod(content);
		currentClass.getMethods().add(methodDefinition);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.wgs.generator.script.gen.PatternGenerator#addClassMethodContent(java
	 * .util.List)
	 */
	public void addClassMethodContent(List<String> content) {
//		System.out.println("{methodContent:" + content + "}");
		String line = "";
		for (String string : content) {
			line += string;
		}
		// line += ";";
		String current = methodDefinition.getContent();
		if (current != null) {
			line = current + line;
		}
		methodDefinition.setContent(line);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.wgs.generator.script.gen.PatternGenerator#addClassProperty(java.util
	 * .List)
	 */
	public void addClassProperty(List<String> props) {
		currentClass.addBeanDefiniation(getBeanDefinition(props));
	}

	@Override
	public void addCode(List<String> unresolvedTokens) {
//		System.out.println("{code:" + unresolvedTokens + "}");
		String line = "";
		for (String string : unresolvedTokens) {
			line += string;
		}
		// line += ";";
		String current = currentClass.getCode();
		if (current != null) {
			line = current + line;
		}
		currentClass.setCode(line);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.wgs.generator.script.gen.PatternGenerator#addImport(java.util.List)
	 */
	public void addImport(List<String> unresolvedTokens) {
		imports.add(unresolvedTokens.get(0));
	}

	private GenBeanDefinition getBeanDefinition(List<String> props) {
		props.remove(" ");

		if (props.size() == 2) {
			return new GenBeanDefinition(props.get(1), props.get(0), null);
		} else {
//			System.out.println("******" + props);
			return new GenBeanDefinition(props.get(1), props.get(0), OptionsUtils.getOptions(props.subList(2, props.size())));
		}
	}

	public String write(String toDir) {
		preWrite();

		String res = "";
		for (ClassDef classDef : classDefs.values()) {

			if (classDef.getOptions().contains("preserve")) {
				File classFile = new File(toDir + "/" + classDef.getPackageName().replace('.', '/') + "/" + classDef.getClassName() + ".java");
				if (classFile.exists()) {
//					System.out.println("not re-creating " + classDef.getClassName());
					continue;
				}
			}

			File dir = new File(toDir + "/" + classDef.getPackageName().replace('.', '/'));
			dir.mkdirs();

			classDef.addBeanDefiniations(properties);

			// reconcile the methods
			for (MethodDefinition methodDefinition : classDef.getMethods()) {
				boolean found = false;
				for (MethodDefinition existingDefinition : classDef.getMethodDefinitions()) {
					if (existingDefinition.getName().equals(methodDefinition.getName())) {
						existingDefinition.setContent(methodDefinition.getContent());
						found = true;
						break;
					}
				}
				if (!found) {
					classDef.addMethod(methodDefinition);
				}
			}

			String classContent = classDef.toString();

			FileOutputStream fileOutputStream = null;
			try {
				fileOutputStream = new FileOutputStream(dir.getAbsolutePath() + "/" + classDef.getClassName() + ".java.tmp");
				
				Jalopy jalopy = new Jalopy();
				fileOutputStream.write(classContent.getBytes());
			} catch (Exception e) {
//				System.err.println();
				e.printStackTrace();
			} finally {
				if (fileOutputStream != null) {
					try {
						fileOutputStream.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
			
			try {
				Jalopy jalopy = new Jalopy();
				File tmp = new File(dir.getAbsolutePath() + "/" + classDef.getClassName() + ".java.tmp");
				jalopy.setInput(tmp);
				jalopy.setOutput(new File(dir.getAbsolutePath() + "/" + classDef.getClassName() + ".java"));
				jalopy.format(true);
				tmp.delete();
			} catch (Exception e) {
				e.printStackTrace();
			}
			
			res += classContent;
		}

		for (Map.Entry<String, String> template : templateDefs.entrySet()) {

			File file = new File(toDir + "/" + template.getKey());
			file.getParentFile().mkdirs();

			FileOutputStream fileOutputStream = null;
			try {
				fileOutputStream = new FileOutputStream(file);
				TemplateReader.parse(ClassLoader.getSystemResourceAsStream(template.getValue()), fileOutputStream, templateProperties);
			} catch (Exception e) {
//				System.err.println(template);
				e.printStackTrace();
			} finally {
				if (fileOutputStream != null) {
					try {
						fileOutputStream.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		}

		return res;
	}

	public abstract void preWrite();

}
